/*
  TODO for future: 
    implement (a|m|c)time, ownership and permissions
    
  Food for thought:   
    What do we do for st_size, st_objsize, st_objects? 
    For files, at least st_size still makes sense. But what do we do 
      for directories? Let's set it to zero for the moment 
*/

#define FUSE_USE_VERSION 26

#include <fuse.h>
#include <dirent.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <assert.h>
#include "object.h"
#include "util.h"
#include "dirops.h"
#include "cache.h"
#include "amazon.h"
#include "cloud_sync.h"
#include "object_dir.h"
#include "crypt.h"

//TODO: Sanitize all paths


void dump_dir (object *dir) {
  assert (dir->type == DIR_OBJ);

  int max = dir->length/(sizeof (dentry));
  int i;

  debug (">>>\n");
  debug ("DUMP_DIR:\t Name\t OBJ_ID\n");
  for (i=0; i<max; i++) {
    dentry *d = (dentry *) (dir->data + (i*sizeof(dentry)));
    debug ("\t\t%s\t %d-%s\n", d->name, (d->obj_id).type, (d->obj_id).id);
  }
  debug (">>>\n");
}

void dump_file (object *file) {
  assert (file->type == FILE_OBJ);

  int max = file->length/(sizeof (fentry));
  int i;

  debug (">>>\n");
  debug ("DUMP FILE:\t ObjectID\t Offset\t Size\n");
  for (i=0; i<max; i++) {
    fentry *f = (fentry *) (file->data + (i*sizeof(fentry)));
    debug ("\t\t%d-%s\t %d\t %d\n", (f->obj_id).type, 
                    (f->obj_id).id, (int) f->offset, (int) f->size);
  }
  debug (">>>\n");
}


/* Creates a new ID (possibly for a new object) */
void new_id (char *buf) {
  char tmp[IDLEN+1];
  snprintf (tmp, IDLEN+1, "%d%d%d", rand (), rand (), rand());
  memcpy (buf, tmp, IDLEN);
}

/* Returns 0 if sparse, -1 if not */
int is_sparse (object_id *id) {
  assert (id->type == DATA_OBJ);
  char zero[IDLEN];
  memset (zero, '0', IDLEN);
  if (strncmp (id->id, zero, IDLEN) == 0) 
    return 0;
  else
    return -1;
}


void *megh_init(struct fuse_conn_info *conn) {
  debug ("init()\n");
  init_cache ();
  load_config ("/home/mor/.megh");
  init_sync_queue ();
  init_object_dir ();

  /* Scan the cache directory */
  DIR *dp = opendir (PREFIX);
  assert (dp != NULL);
  struct dirent *dent;

  dent = readdir (dp);
  while (dent != NULL) {
    if (dent->d_name[0] != '.') {
      add_to_object_dir (dent->d_name);
      set_disk_field (dent->d_name, 1);
      push_to_sync_queue (dent->d_name);
    }
    dent = readdir (dp);
  }
  closedir (dp);

  pthread_t syncserv_id;
  pthread_create (&syncserv_id, NULL, sync_loop, NULL);
  return NULL;
}

void megh_destroy (void *init_ret) {
  debug ("destroy()\n");
  destroy_cache ();
  //TODO: safely finish off the object service
  sleep (SLEEP_TIME+20);
}

/* takes a file path, and fills up corresponding stat info */
int megh_getattr (const char *path, struct stat *stbuf) {
  dump_cache ();

  /* Get the object, and then fill up stbuf */

  object *obj = malloc (sizeof (object));
  void *buf = malloc (MAX_DATA_OBJ);
  int ret = get_object_from_path (path, obj, MAX_DATA_OBJ, buf);

  if (ret == 0) {
    /* No errors */
    assert (obj->type != DATA_OBJ);   /* No path is associated */ 
    memset (stbuf, 0, sizeof (struct stat));

    // TODO: implement permission, ownerships, [a|c|m]time
    if (obj->type == DIR_OBJ) {
      stbuf->st_mode = S_IFDIR|0755;
      stbuf->st_size = obj->length;
      /* We really don't go to multiple blocks for a directory */
      stbuf->st_blocks = 1; 
    }
    else {
      stbuf->st_mode = S_IFREG|0644;
      /* Here's how we are going to do it:
         Take the last fentry, look at the offset, and add the size */
      if (obj->length == 0) 
        stbuf->st_size = 0;
      else
        stbuf->st_size = 
            ((fentry *)(obj->data + obj->length - sizeof(fentry)))->offset +
            ((fentry *)(obj->data + obj->length - sizeof(fentry)))->size;

      /* Let's put number of blocks same as number of data objects */
      stbuf->st_blocks = (obj->length)/(sizeof (fentry));
    }

    /* No notion of user-id and group-id as of now */
    stbuf->st_uid = fuse_get_context()->uid;
    stbuf->st_gid = fuse_get_context()->gid;
    stbuf->st_blksize = MAX_DATA_OBJ;
    stbuf->st_nlink = 1;   
 
  } 

  free (buf);
  free (obj);
  return ret;
}

int megh_access (const char *path, int mask) {

  //TODO: use mask

  /* If I can get the object, we are good */
  object *obj = malloc (sizeof (object));
  void *buf = malloc (MAX_DATA_OBJ);
  int ret = get_object_from_path (path, obj, MAX_DATA_OBJ, buf);
  free (buf);
  free (obj);
  return ret;
}

int megh_opendir (const char *path, struct fuse_file_info *f) {

  /* Let's not use fuse_file_info as of now.
     Just check whether the directory exists or not */

  object *obj = malloc (sizeof (object));
  void *buf = malloc (MAX_DATA_OBJ);
  int ret = get_object_from_path (path, obj, MAX_DATA_OBJ, buf);
  if (ret == 0 && obj->type != DIR_OBJ)
    ret = -ENOTDIR;
  free (buf);
  free (obj);
  return ret;

}


int megh_readdir (const char *path, void *buf, fuse_fill_dir_t filler, 
                off_t offset, struct fuse_file_info* f) {


  object *dir = malloc (sizeof (object));
  void *dir_buf = malloc (MAX_DATA_OBJ);

  int ret = get_object_from_path (path, dir, MAX_DATA_OBJ, dir_buf);

  if (ret == 0 && dir->type != DIR_OBJ)
    ret = -ENOTDIR;

  if (ret == 0) {
    //TODO
    /* Let's just dig into dentry for one last time ;-) */

    dentry *d = (dentry *) dir->data;
    int num_dentry = (dir->length)/(sizeof (dentry));
    int i;
    for (i=0; i<num_dentry; i++) {
      if (filler (buf, d[i].name, NULL, 0) != 0) {
        ret = -ENOMEM;
        break;
      }
    }
  }

  free (dir_buf);
  free (dir);

  return ret;
}


int megh_mkdir (const char *path, mode_t mode) {

  //TODO: use mode
  char *parent = malloc ((strlen (path)+1));
  char name[MAX_FNAME_LEN];

  get_parent (path, parent);
  get_filename (path, name);

  object *parent_object = malloc (sizeof (object));
  void *parent_buf = malloc (MAX_DATA_OBJ);

  int ret = get_object_from_path (parent, parent_object, 
                                  MAX_DATA_OBJ, parent_buf);

  if (ret == 0 && parent_object->type != DIR_OBJ)
    ret = -ENOTDIR;

  if (ret == 0) {

    /* Create a new object */
    object *obj = malloc (sizeof (object));
    memset (obj, 0, sizeof (object));
    obj->type = DIR_OBJ;
    obj->length = 0;
    obj->data = NULL;

    /* Create an ID for the new object */
    object_id *id = malloc (sizeof (object_id));
    id->type = DIR_OBJ;
    new_id (id->id);
    id->ver = id->dev = 0;
    gen_key (32, (id->key).key);
    gen_key (32, (id->key).iv);
 
    /* Save the object */
    put_object_c (id, obj);

    /* Create a new dentry, and insert it */
    dentry *d = malloc (sizeof (dentry));
    memset (d, 0, sizeof (dentry));

    strcpy (d->name, name);
    memcpy (&(d->obj_id), id, sizeof (object_id));

    ret = dentry_insert (parent_object, d);
    /* Now save the parent object again */
    if (ret == 0) {
      ret = save_object_with_path (parent, parent_object);
    }

    free (d);
    free (id);
    free (obj);
  }

  // TODO; think of a cleaner implementation.
  free (parent_buf);
  free (parent_object);
  free (parent);

  return ret;
}

/* Should succeed only when the directory is empty */
int megh_rmdir (const char *path) {
  /* Two parts: check for directory to be removed as empty
          and   update the parent directory */

  object *obj = malloc (sizeof (object));
  void *buf = malloc (MAX_DATA_OBJ);

  int ret = get_object_from_path (path, obj, MAX_DATA_OBJ, buf);

  if (ret == 0) {
    if (obj->type != DIR_OBJ)
      ret = -ENOTDIR;
    if (obj->length != 0) 
      ret = -ENOTEMPTY;
  }

  if (ret == 0) {

    char *parent_path = malloc (strlen(path)+1);
    char name[MAX_FNAME_LEN];

    get_parent (path, parent_path);
    get_filename (path, name);

    object *parent_object = malloc (sizeof (object));
    void *parent_buf = malloc (MAX_DATA_OBJ);

    ret = get_object_from_path (parent_path, parent_object,
                                  MAX_DATA_OBJ, parent_buf);

    assert (ret == 0);

    ret = dentry_delete (parent_object, name);

    /* Write the parent object */
    ret = save_object_with_path (parent_path, parent_object);
    
    free (parent_buf);
    free (parent_object);
    free (parent_path);
  }

  free (buf);
  free (obj);

  return ret;
}

/*
  Inserts the fentry F at INDEXth position (starts from 1) in FILE
  Returns 0 in case of success, -1 in case of failure
*/

int fentry_insert (object *file, fentry *f, int index) {
  if (file->length >= MAX_DATA_OBJ + sizeof(fentry)) {
    return -1; /* Too long file */
  }

  int i;
  fentry *fentlist = (fentry *)file->data;
  int num_fentry = file->length/ (sizeof (fentry));
  for (i=num_fentry-1 ; i>=index ; i--) {
    memcpy (fentlist+i+1, fentlist+i, sizeof (fentry));
  }

  /* Finally copy the new fentry */
  memcpy (fentlist+index, f, sizeof (fentry));
  
  /* Also increase the length of the object */
  file->length += sizeof (fentry);

  return 0;
}

/*
  Returns non-negative in case there exists a data object corresponding 
  to OFFSET, where the return value indicates the index (starting from 0)
  of the data-object corresponding to the OFFSET.
  Return -1 in case there is no such object (this could happen in case of
  append, or sparse files)
*/
int find_index (object *obj, off_t offset) {
  int num_fentry = obj->length/ (sizeof(fentry));
  int i;
  fentry *fentlist = (fentry *) obj->data;

  for (i=0; i<num_fentry; i++) {
    if (fentlist[i].offset <= offset && 
        offset < fentlist[i].offset + (off_t) fentlist[i].size)
      return 0;
  }
  return -1;
}


/* Similar to access */
int megh_open (const char* path, struct fuse_file_info* f) {

  //TODO: fix this
  return megh_access (path, 0000);
} 

int megh_truncate (const char *path, off_t size) {

  object *file = malloc (sizeof (object));
  void *buf = malloc (MAX_DATA_OBJ);

  int ret = get_object_from_path (path, file, MAX_DATA_OBJ, buf);

  if (ret == 0) {

    assert (file->type == FILE_OBJ);

    /* 
      What we need to do is: 
      - if the file size is more than SIZE, reduce it
      - if the file size is less than SIZE, increase it
    */

    off_t current_size = 0;
    if (file->length != 0) {
      fentry *last_fentry = ((fentry *)(file->data + file->length))-1;
      current_size = last_fentry->offset + last_fentry->size; 
    }


    if (current_size <= size) {
      /* Add some zeroed data objects */
      size_t to_write = size-current_size;
      size_t remaining = to_write;

      /* We need a fentry corresponding to sparse file, 
         and an object ID of special type 
      */
      object_id *zero_obj_id = malloc (sizeof (object_id));
      memset (zero_obj_id, 0, sizeof (object_id));

      zero_obj_id->type = DATA_OBJ;
      memset (zero_obj_id->id, '0', IDLEN);
      zero_obj_id->ver = zero_obj_id->dev = 0;

      fentry *sparse_fentry = malloc (sizeof (fentry));
      memset (sparse_fentry, 0, sizeof (fentry));

      while (remaining > 0) {

        memcpy (&(sparse_fentry->obj_id), zero_obj_id, sizeof (object_id));
        sparse_fentry->offset = current_size + (to_write-remaining);
        sparse_fentry->size = remaining < MAX_DATA_OBJ ? 
                                        remaining : MAX_DATA_OBJ;

        fentry_insert (file, sparse_fentry, (file->length)/(sizeof (fentry)));
        remaining -= sparse_fentry->size;
      }

      free (sparse_fentry);
      free (zero_obj_id);

    } else {
      /* 
        Remove some of the fentries, and possibly rewrite the last
        data object.
       */

      while (current_size > size) {

        /* Current size must definitely be > 0, which means file->length !=0 */
        assert (file->length > 0);

        fentry *last_fentry = ((fentry *)(file->data+file->length))-1;
        if (size <= last_fentry->offset) {

          /* Delete the last fentry, which just means that we reduce the 
             file length by one fentry */
          file->length -= sizeof(fentry);

        } else {
          /* We need to chop down the object corresponding to this 
             fentry to match the length requirement */

          /* The new length will be less than max because of our invariants */
          int new_length = size - last_fentry->offset;

          if (is_sparse (&(last_fentry->obj_id)) == 0) {

            /* A sparse object, we just change the size in the fentry, 
               because this object will never be invoked via get_object */
            last_fentry->size = new_length;
            
            /* That's it. I think we are done */
          } else {

            /* The object is not sparse. So we need to truncate the object */
        
            object *old_object = malloc (sizeof (object));
            void *buf = malloc (MAX_DATA_OBJ);

            get_object_c (&(last_fentry->obj_id), old_object,
                                            MAX_DATA_OBJ, buf);

            old_object->length = new_length;
            put_object_c (&(last_fentry->obj_id), old_object);

            last_fentry->size = new_length;

            free (buf);
            free (old_object);
          }
        }    
        

        /* Update current size */
        current_size = 0;
        if (file->length != 0) {
          last_fentry = ((fentry *)(file->data + file->length))-1;
          current_size = last_fentry->offset + last_fentry->size; 
        }
      }
    }
  }

  save_object_with_path (path, file);

  free (buf);
  free (file);

  return ret;
}


int write_helper (object *file, const char *buf, size_t size, off_t offset) {

  fentry *fentlist = file->data;
  int i=0;
  off_t byte_ptr = offset; 
  size_t remaining = size;

  while (remaining > 0) {

    if (i<(int)(file->length/(sizeof(fentry)))) {
      /* We are not at the end of the file */

      if (fentlist[i].offset <= byte_ptr && 
              byte_ptr < (fentlist[i].offset + (off_t) fentlist[i].size)) {


        /* Bingo !!! */

        /* This is the most tricky part. Let's say you want to 
           change a few bytes in the middle of a data object. It's 
           better to write a new object, with part of it coming from
           the old data object, and the remaining coming from the
           new data that we are going to write.
         */


        /* We do not need to create a new object. Just get the old object,
           and overwrite part of the buffer. And similarly, we do not need 
           to create a new objectid, insert it again into the file object */

        object *old_object = malloc (sizeof (object));
        void *old_object_buf = malloc (MAX_DATA_OBJ);

        if (is_sparse (&(fentlist[i].obj_id)) == 0) {
          /* This is a sparse object. Let's just fill it with zeros */
          memset (old_object_buf, 0, MAX_DATA_OBJ);
          old_object->type = DATA_OBJ;
          old_object->length = fentlist[i].size;
          old_object->data = old_object_buf;
        } else {
          /* This is a normal object */
          get_object_c (&(fentlist[i].obj_id), old_object, 
                                MAX_DATA_OBJ, old_object_buf);
        }

        size_t min = remaining < 
              (old_object->length - (byte_ptr-fentlist[i].offset)) ? remaining :
              (old_object->length - (byte_ptr-fentlist[i].offset));    


        memcpy (old_object->data + (byte_ptr - fentlist[i].offset), 
                                        buf + (size-remaining), min);

        /* In case this is a sparse object, we need to generate a 
           new object_id as well 
        */
        if (is_sparse (&(fentlist[i].obj_id)) == 0) {
          new_id ((fentlist[i].obj_id).id);
          gen_key (32, ((fentlist[i].obj_id).key).key);
          gen_key (32, ((fentlist[i].obj_id).key).iv);
        }
        /* Let's put this object in its proper place */
        put_object_c (&(fentlist[i].obj_id), old_object);

        /* Now update things  */ 
        /* No update to i. Think thorugh it */
        i++;
        byte_ptr += min;
        remaining -= min;

        /* Free up things */
        free (old_object_buf);
        free (old_object);
 

      } else {
        /* 
          This is the case when we still haven't reached the correct 
          fentry. Something like: we need to seek till a very large
          number, and we are still at the very beginning.
          Just increase i, and hope for the best 
        */
        i++;
      }
    } else {
      /* 
        We have reached the end of file. Let's just create objects of
        length MAX_DATA_OBJ, write those objects, and insert fentries 
      */

      /*
        If the previous object is too small, let's not create a new 
        data object. Instead, let's just pad it to make it bigger. This 
        way, we'll not have many small objects. 
      */

      if (file->length !=0 && 
        fentlist[((file->length)/sizeof(fentry))-1].size < MAX_DATA_OBJ) {

        debug ("Going great \n");

        object *old_object = malloc (sizeof (object));
        void *old_object_buf = malloc (MAX_DATA_OBJ);
        memset (old_object_buf, 0, MAX_DATA_OBJ);

        get_object_c (&(fentlist[((file->length)/sizeof(fentry))-1].obj_id), 
                      old_object, MAX_DATA_OBJ, old_object_buf);

        size_t to_write = remaining < (MAX_DATA_OBJ-old_object->length) ?
                          remaining : (MAX_DATA_OBJ-old_object->length);

        /* Now update the old object */
        memcpy (old_object_buf + old_object->length, 
                        buf + (size-remaining), to_write);

        old_object->length += to_write;
        fentlist[((file->length)/sizeof(fentry))-1].size += to_write;;

        put_object_c (&(fentlist[((file->length)/sizeof(fentry))-1].obj_id),
                              old_object);

        byte_ptr += to_write;
        remaining -= to_write;

        free (old_object_buf);
        free (old_object);


      } else {

        debug ("Going not so great \n");
        /* The previous object is already full. We do need to create a 
           new object */

        /* Get the object in place */
        object *new_object = malloc (sizeof (object));
        void *new_object_buf = malloc (MAX_DATA_OBJ);
        memset (new_object_buf, 0, MAX_DATA_OBJ);

        memset (new_object, 0, sizeof (object));
        new_object->type = DATA_OBJ;
        new_object->length = 
                remaining < MAX_DATA_OBJ ? remaining : MAX_DATA_OBJ;
        new_object->data = new_object_buf;
        memcpy (new_object_buf, buf+(size-remaining), new_object->length);

        /* Figure out the object_id */
        object_id *new_object_id = malloc (sizeof (object_id));
        memset (new_object_id, 0, sizeof (object_id));
        new_object_id->type = DATA_OBJ;
        new_id (new_object_id->id);
        new_object_id->ver = new_object_id->dev = 0;
        gen_key (32, (new_object_id->key).key);
        gen_key (32, (new_object_id->key).iv);

        /* Let's put this object in its proper place */
        put_object_c (new_object_id, new_object);


        /* Let's create a file entry to insert into the file object */
        fentry *new_fentry = malloc (sizeof (fentry));
        memcpy (&(new_fentry->obj_id), new_object_id, sizeof (object_id));
        new_fentry->offset = byte_ptr;
        new_fentry->size = new_object->length;

        fentry_insert (file, new_fentry, i);

        /* Now update things  */ 
        i++;
        byte_ptr += new_object->length;
        remaining -= new_object->length;

        /* Free up things */
        free (new_fentry);
        free (new_object_id);
        free (new_object_buf);
        free (new_object);
      }
    }
  }
  
  //TODO: handle errors 
  return 0;
}


int megh_write (const char *path, const char *buf, size_t size, 
                        off_t offset, struct fuse_file_info* f) {

  object *obj = malloc (sizeof (object));
  void *obj_buf = malloc (MAX_DATA_OBJ);

  int ret = get_object_from_path (path, obj, MAX_DATA_OBJ, obj_buf);
  if (ret == 0 && obj->type != FILE_OBJ) {
    ret = -EBADF;
  }

  /*
    File object contains File Entries (fentry), with the offset in
    increasing order. 
    Invariant: 
      Offset (ith fentry) + size (ith fentry) = Offset (i+1 th fentry)
  */

  if (ret == 0) {
    /* 
      Writes out new data objects, and modifies the in-memory file object
    */

    ret = write_helper (obj, buf, size, offset);
    if (ret == 0)
      ret = save_object_with_path (path, obj);
  }

  /*
    I think we need to return the number of bytes actually written,
    lets do that :D 
  */
  ret = size;

  free (obj_buf);
  free (obj);
  return ret;
}


//TODO
int megh_read (const char* path, char *buf, size_t size, 
                          off_t offset, struct fuse_file_info* f) {
  /* 
    If the offset is beyond the file length, return 0. If there 
    are sparse things in between, fill up the buffer with zero.
  */

  object *file = malloc (sizeof (object));
  void *file_buf = malloc (MAX_DATA_OBJ);
  
  size_t remaining = size;

  int ret = get_object_from_path (path, file, MAX_DATA_OBJ, file_buf);
  if (ret == 0) {

    int num_fentry = file->length/(sizeof (fentry));
    fentry *fentlist = (fentry *)file->data;

    off_t byte_ptr = offset;
    int i = 0;

    while (remaining > 0) {

      if (i<num_fentry) {
 
        if (fentlist[i].offset <= byte_ptr && 
                byte_ptr < (fentlist[i].offset + (off_t) fentlist[i].size)) {

          /* Bingo. Read from this object as soon as you can */

          object *data_obj = malloc (sizeof (object));
          void *data_obj_buf = malloc (MAX_DATA_OBJ);

          if (is_sparse (&(fentlist[i].obj_id)) == 0 ) {
            /* This is a sparse object */
            memset (data_obj_buf, 0, MAX_DATA_OBJ);
            data_obj->type = DATA_OBJ;
            data_obj->length = fentlist[i].size;
            data_obj->data = data_obj_buf;

          } else {
            get_object_c (&(fentlist[i].obj_id), data_obj, 
                                    MAX_DATA_OBJ, data_obj_buf);
          }

          assert (data_obj->type == DATA_OBJ);

          off_t obj_offset = byte_ptr - fentlist[i].offset;
          size_t to_be_read = remaining < (data_obj->length - obj_offset) 
                      ? remaining : (data_obj->length - obj_offset);

          memcpy (buf + (byte_ptr - offset), data_obj->data + obj_offset, 
                                                to_be_read);         
 
          remaining -= to_be_read;
          byte_ptr += to_be_read;

          i++;          
          free (data_obj_buf);
          free (data_obj);

        } else {
          i++;
        }
      } else {
        /* We have hit the end of file */
        break;
      }
    } 
  }

  free (file_buf);
  free (file);

  return (size-remaining);
}

int megh_create (const char *path, mode_t mode, struct fuse_file_info *f) {

  char *parent_path = malloc (strlen (path)+1);
  char filename[MAX_FNAME_LEN];

  get_parent (path, parent_path);
  get_filename (path, filename);

  object *parent_object = malloc (sizeof (object));
  void *parent_object_buf = malloc (MAX_DATA_OBJ);
  
  int ret = get_object_from_path (parent_path, parent_object, 
                                MAX_DATA_OBJ, parent_object_buf);

  if (ret == 0) {

    /* Let's create a new object */
    object *new_object = malloc (sizeof (object));

    memset (new_object, 0, sizeof (object));
    new_object->type = FILE_OBJ;  /* Open is a call for a file */ 
    new_object->length = 0;
    new_object->data = NULL;


    /* Let's create a new object_id */
    object_id *new_object_id = malloc (sizeof (object_id));
    memset (new_object_id, 0, sizeof (object_id));

    new_object_id->type = FILE_OBJ;
    new_id (new_object_id->id);
    new_object_id->ver = new_object_id->dev = 0;
    gen_key (32, (new_object_id->key).key);
    gen_key (32, (new_object_id->key).iv);

    put_object_c (new_object_id, new_object);

    dentry *d = malloc (sizeof (dentry));
    memset (d, 0, sizeof (dentry));

    strcpy (d->name, filename);
    memcpy (&(d->obj_id), new_object_id, sizeof (object_id));
    
    ret = dentry_insert (parent_object, d);

    if (ret == 0) {
      ret = save_object_with_path (parent_path, parent_object);
    }

    free (d);
    free (new_object_id);
    free (new_object);
  }

  free (parent_object_buf);
  free (parent_object);
  free (parent_path);
  return ret;
}

int megh_unlink (const char *path) {

  object *obj = malloc (sizeof (object));
  void *buf = malloc (MAX_DATA_OBJ);

  int ret = get_object_from_path (path, obj, MAX_DATA_OBJ, buf);

  if (ret == 0) {
    if (obj->type != FILE_OBJ)
      ret = -ENOENT;
  }

  if (ret == 0) {

    char *parent_path = malloc (strlen(path)+1);
    char name[MAX_FNAME_LEN];

    get_parent (path, parent_path);
    get_filename (path, name);

    object *parent_object = malloc (sizeof (object));
    void *parent_buf = malloc (MAX_DATA_OBJ);

    ret = get_object_from_path (parent_path, parent_object,
                                  MAX_DATA_OBJ, parent_buf);

    assert (ret == 0);

    ret = dentry_delete (parent_object, name);

    /* Write the parent object */
    ret = save_object_with_path (parent_path, parent_object);
    
    free (parent_buf);
    free (parent_object);
    free (parent_path);
  }

  free (buf);
  free (obj);

  return ret;
}


int megh_flush (const char *path, struct fuse_file_info *f) {

  // TODO
  return 0;

}

struct fuse_operations megh_operations = {
  .init         = megh_init,
  .destroy      = megh_destroy,
  .getattr      = megh_getattr,
  .access       = megh_access,
  .opendir      = megh_opendir,
  .readdir      = megh_readdir,
  .mkdir        = megh_mkdir,
  .rmdir        = megh_rmdir,
  .open         = megh_open,
  .truncate     = megh_truncate,
  .write        = megh_write,
  .read         = megh_read,
  .create       = megh_create,  
  .unlink       = megh_unlink,
  .flush        = megh_flush
};

int main (int argc, char *argv[]) {
  srand ( (unsigned int) time (NULL));
  return fuse_main (argc, argv, &megh_operations, NULL);
}
